/**
 * @file oled.c
 * @brief OLED驱动API
 * @author wangh (wanghuan3037@fiberhome.com)
 * @version 1.0
 * @date 2020-10-13
 * @copyright Copyright (c) 2020  烽火通信
 ***************************************************************
 * @par 修改日志:
 * <table>
 * <tr><th>Date       <th>Version  <th>Author  <th>Description
 * <tr><td>2020-10-13 <td>1.0      <td>wangh   <td>内容
 * </table>
 */
#include "oled.h"
#if DRV_OLED_EN
#include "oledfont.h"

#include "FreeRTOS.h"
#include "task.h"


static uint8_t OLED_GRAM[128][8];   ///< OLED显存

/**
 * @brief 向SSD1306写入一个字节
 * @param  obj         OLED对象
 * @param  data        要写入的数据/命令
 * @param  cmd         数据/命令标志 0,表示命令;1,表示数据;
 */
static void OledWriteByte( Oled_t *obj, uint8_t data, uint8_t cmd )
{
    GpioWrite( &obj->Oled_dc, cmd );
#if OLED_USE_CS
    GpioWrite( &obj->Oled_cs, 0 );
#endif
#if OLED_USE_SPI
    SpiInOut( &obj->spi_obj, data );
#else
    for (uint8_t i = 0; i < 8; i++)
    {
        GpioWrite( &obj->Oled_scl, 0 );
        if (data&0x80)
            GpioWrite( &obj->Oled_sda, 1 );
        else
            GpioWrite( &obj->Oled_sda, 0 );
        GpioWrite( &obj->Oled_scl, 1 );
        data<<=1;
    }
#endif
#if OLED_USE_CS
    GpioWrite( &obj->Oled_cs, 1 );
#endif
    GpioWrite( &obj->Oled_dc, OLED_DATA );
}

/**
 * @brief OLED初始化
 * @param  obj         OLED对象
 * @param  scl         时钟线D0
 * @param  sda         数据线D1
 * @param  dc          数据/命令选择引脚
 * @param  cs          片选线
 * @param  res         复位线
 */
void OledInit( Oled_t *obj, PinNames scl, PinNames sda, PinNames dc, PinNames cs, PinNames res )
{
#if OLED_USE_SPI
    SpiInit( &obj->spi_obj, SPI_3, OLED_SDA, NC, OLED_SCL, NC );
#else
    GpioInit( &obj->Oled_scl, scl, PIN_OUTPUT, PIN_PUSH_PULL, PIN_NO_PULL, 1 );
    GpioInit( &obj->Oled_sda, sda, PIN_OUTPUT, PIN_PUSH_PULL, PIN_NO_PULL, 1 );
    GpioInit( &obj->Oled_dc, dc, PIN_OUTPUT, PIN_PUSH_PULL, PIN_NO_PULL, 1 );
    #if OLED_USE_CS
    GpioInit( &obj->Oled_cs, cs, PIN_OUTPUT, PIN_PUSH_PULL, PIN_NO_PULL, 0 );
    #endif
    #if OLED_USE_RES
    GpioInit( &obj->Oled_res, res, PIN_OUTPUT, PIN_PUSH_PULL, PIN_NO_PULL, 1 );
    GpioWrite( &obj->Oled_res, 0 );
    vTaskDelay(200);
    GpioWrite( &obj->Oled_res, 1 );
    #endif
#endif

#if (OLED_CHIP == SSD1306)
	OledWriteByte( obj, 0xAE, OLED_CMD );   /* 关闭显示 */
	OledWriteByte( obj, 0xD5, OLED_CMD );   /* 设置时钟分频因子,震荡频率 */
	OledWriteByte( obj, 80, OLED_CMD );     /* [3:0],分频因子;[7:4],震荡频率 */
	OledWriteByte( obj, 0xA8, OLED_CMD );   /* 设置驱动路数 */
	OledWriteByte( obj, 0x3F, OLED_CMD );   /* 默认0X3F(1/64)  */
	OledWriteByte( obj, 0xD3, OLED_CMD );   /* 设置显示偏移 */
	OledWriteByte( obj, 0x00, OLED_CMD );   /* 默认为0 */
	
	OledWriteByte( obj, 0x40, OLED_CMD );   /* 设置显示开始行 [5:0],行数 */
	
	OledWriteByte( obj, 0x8D, OLED_CMD );   /* 电荷泵设置 */
	OledWriteByte( obj, 0x14, OLED_CMD );   /* bit2，开启/关闭 */
	OledWriteByte( obj, 0x20, OLED_CMD );   /* 设置内存地址模式 */
	OledWriteByte( obj, 0x02, OLED_CMD );   /* [1:0],00，列地址模式;01，行地址模式;10,页地址模式;默认10 */
	OledWriteByte( obj, 0xA1, OLED_CMD );   /* 段重定义设置,bit0:0,0->0;1,0->127 */
	OledWriteByte( obj, 0xC0, OLED_CMD );   /* 设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 */
	OledWriteByte( obj, 0xDA, OLED_CMD );   /* 设置COM硬件引脚配置 */
	OledWriteByte( obj, 0x12, OLED_CMD );   /* [5:4]配置 */
	
	OledWriteByte( obj, 0x81, OLED_CMD );   /* 对比度设置 */
	OledWriteByte( obj, 0xEF, OLED_CMD );   /* 1~255;默认0X7F (亮度设置,越大越亮) */
	OledWriteByte( obj, 0xD9, OLED_CMD );   /* 设置预充电周期 */
	OledWriteByte( obj, 0xF1, OLED_CMD );   /* [3:0],PHASE 1;[7:4],PHASE 2 */
	OledWriteByte( obj, 0xDB, OLED_CMD );   /* 设置VCOMH 电压倍率 */
	OledWriteByte( obj, 0x30, OLED_CMD );   /* [6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc */
	
	OledWriteByte( obj, 0xA4, OLED_CMD );   /* 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) */
	OledWriteByte( obj, 0xA6, OLED_CMD );   /* 设置显示方式;bit0:1,反相显示;0,正常显示 */
	
#elif (OLED_CHIP == SSD1309)
    /* OLED初始化参数配置 */
    /* Unlock Driver IC (0x12/0x16) */
    OledWriteByte( obj, 0xFD, OLED_CMD );   /* Set Command Lock */
    OledWriteByte( obj, 0x12, OLED_CMD );   /* 0x12 => Driver IC interface is unlocked from entering command. 0x16 => All Commands are locked except 0xFD. */

    /* Display Off (0xAE/0xAF) */
    OledWriteByte( obj, 0xAE, OLED_CMD );   /* Set Display On/Off 0xAE => Display Off 0xAF => Display On */

    /* 设置时钟分频 116 Frames/Sec */
    OledWriteByte( obj, 0xD5, OLED_CMD );   /* Set Command Lock */
    OledWriteByte( obj, 0xA0, OLED_CMD );

    /* 1/64 Duty (0x0F~0x3F) */
    OledWriteByte( obj, 0xA8, OLED_CMD );   /* Set Multiplex Ratio */
    OledWriteByte( obj, 0x3F, OLED_CMD );   /* Default => 0x3F (1/64 Duty) */

    /* Shift Mapping RAM Counter (0x00~0x3F) */
    OledWriteByte( obj, 0xD3, OLED_CMD );   /* Set Display Offset */
    OledWriteByte( obj, 0x00, OLED_CMD );

    /* Set Mapping RAM Display Start Line (0x00~0x3F) */
    OledWriteByte( obj, 0x40|0x00, OLED_CMD );  /* Set Display Start Line */

    /* Set Page Addressing Mode (0x00/0x01/0x02) */
    OledWriteByte( obj, 0x20, OLED_CMD );   /* Set Memory Addressing Mode */
    OledWriteByte( obj, 0x02, OLED_CMD );   /* 0x00 => Horizontal Addressing Mode 0x01 => Vertical Addressing Mode 0x02 => Page Addressing Mode */

    /* Set SEG/Column Mapping (0xA0/0xA1) */
    OledWriteByte( obj, 0xA1, OLED_CMD );   /* 0xA0 => Column Address 0 Mapped to SEG0 0xA1 => Column Address 0 Mapped to SEG127 */

    /* Set COM/Row Scan Direction (0xC0/0xC8) */
    OledWriteByte( obj, 0xC8, OLED_CMD );   /* 0xC0 => Scan from COM0 to 63 0xC8 => Scan from COM63 to 0 */

    /* Set Alternative Configuration (0x02/0x12) */
    OledWriteByte( obj, 0xDA, OLED_CMD );   /* Set COM Pins Hardware Configuration */
    OledWriteByte( obj, 0x12, OLED_CMD );   /* Disable COM Left/Right Re-Map */

    /* Set SEG Output Current */
    OledWriteByte( obj, 0x81, OLED_CMD );   /* Set Contrast Control for Bank 0 */
    OledWriteByte( obj, 0x7F, OLED_CMD );

    /* Set Pre-Charge as 2 Clocks & Discharge as 5 Clocks */
    OledWriteByte( obj, 0xD9, OLED_CMD );   /* Set Pre-Charge Period */
    OledWriteByte( obj, 0x25, OLED_CMD );   /* TODO:0x82 */

    /* Set VCOMH Deselect Level */
    OledWriteByte( obj, 0xDB, OLED_CMD );
    OledWriteByte( obj, 0x34, OLED_CMD );   /* Default => 0x34 (0.78*VCC) */

    OledWriteByte( obj, 0xA4, OLED_CMD );   /* Set Entire Display On/Off (0xA4/0xA5) */
    OledWriteByte( obj, 0xA6, OLED_CMD );   /* Set Normal/Inverse Display (0xA6/0xA7) */
#endif

    OledDisplayOn( obj );   /* 开启显示 */
    OledClear( obj );       /* 清屏 */
}


/**
 * @brief 更新显存到OLED
 * @param  obj         OLED对象
 */
void OledRefreshGram( Oled_t *obj )
{
    uint8_t i, n;
    for (i = 0; i < 8; i++)
    {
        OledWriteByte( obj, 0xb0+i, OLED_CMD ); /* 设置页地址（0~7） */
        OledWriteByte( obj, 0x00, OLED_CMD);    /* 设置显示位置—列低地址 */
        OledWriteByte( obj, 0x10, OLED_CMD);    /* 设置显示位置—列高地址 */
        for (n = 0; n < 128; n++)
        {
            OledWriteByte( obj, OLED_GRAM[n][i], OLED_DATA);
        }
    }
}

/**
 * @brief 开启OLED显示
 * @param  obj         OLED对象
 */
void OledDisplayOn( Oled_t *obj )
{
#if (OLED_CHIP == SSD1306)
    OledWriteByte( obj, 0X8D, OLED_CMD );   /* SET DCDC命令 */
    OledWriteByte( obj, 0X14, OLED_CMD );   /* DCDC ON */
    OledWriteByte( obj, 0XAF, OLED_CMD );   /* DISPLAY ON */
#elif (OLED_CHIP == SSD1309)
    OledWriteByte( obj, 0XAF, OLED_CMD );   /* DISPLAY ON */
#endif
}

/**
 * @brief 关闭OLED显示
 * @param  obj         OLED对象
 */
void OledDisplayOff( Oled_t *obj )
{
#if (OLED_CHIP == SSD1306)
    OledWriteByte( obj, 0X8D, OLED_CMD );   /* SET DCDC命令 */
    OledWriteByte( obj, 0X10, OLED_CMD );   /* DCDC OFF */
    OledWriteByte( obj, 0XAE, OLED_CMD );   /* DISPLAY OFF */
#elif (OLED_CHIP == SSD1309)
    OledWriteByte( obj, 0XAE, OLED_CMD );   /* DISPLAY OFF */
#endif
}

/**
 * @brief OLED清屏
 * @param  obj         OLED对象
 */
void OledClear( Oled_t *obj )
{
    uint8_t i, n;
    for (i = 0; i < 8; i++)
    {
        for (n = 0; n < 128; n++)
        {
            OLED_GRAM[n][i] = 0X00;
        }
    }
    OledRefreshGram( obj ); /* 更新显示 */
}

/**
 * @brief OLED12864画点
 * @param  obj         OLED对象
 * @param  x           x坐标
 * @param  y           y坐标
 * @param  dot         1 填充 0,清空
 */
static void OledDrawPoint( Oled_t *obj, uint8_t x, uint8_t y, uint8_t dot )
{
    if ((x > 127) || (y > 63))  /* 超出范围 */
        return;

    if(dot)
        OLED_GRAM[x][7-y/8] |= 1<<(7-y%8);
    else
        OLED_GRAM[x][7-y/8] &= ~(1<<(7-y%8));
}

/**
 * @brief OLED填充
 * @param  obj         OLED对象
 * @param  x1,y1,x2,y2 填充区域的对角坐标
 * @param  dot         0,清空;1,填充
 */
void OledFill( Oled_t *obj, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t dot)
{
    uint8_t x, y;
    for (x = x1; x <= x2; x++)
    {
        for (y = y1; y <= y2; y++)
            OledDrawPoint( obj, x, y, dot );
    }
    OledRefreshGram( obj ); /* 更新显示 */
}

/**
 * @brief 在指定位置显示一个字符,包括部分字符
 * @param  obj         OLED对象
 * @param  x           x坐标
 * @param  y           y坐标
 * @param  chr         字符
 * @param  size        字体大小
 * @param  mode        0,反白显示;1,正常显示
 */
void OledShowChar( Oled_t *obj, uint8_t x, uint8_t y, uint8_t chr, uint8_t size, uint8_t mode)
{
    uint8_t temp, t, t1;
    uint8_t y0 = y;
    uint8_t csize = (size/8 + ((size%8)?1:0)) * (size/2);   /* 得到字体一个字符对应点阵集所占的字节数 */
    chr = chr-' ';  /* 得到偏移后的值 */

    for(t=0; t<csize; t++)
    {
        if(size==12) 
            temp = asc2_1206[chr][t];   /* 调用1206字体 */
        else if(size==16)
            temp=asc2_1608[chr][t];     /* 调用1608字体 */
        else if(size==24)
            temp=asc2_2412[chr][t];     /* 调用2412字体 */
        else 
            return;     /* 没有的字库 */

        for(t1=0; t1<8; t1++)
        {
            if(temp&0x80)
                OledDrawPoint( obj, x, y, mode );
            else
                OledDrawPoint( obj, x, y, !mode );
            temp <<= 1;
            y++;
            if(y >= 64) /* 超区域了 */
                return;

            if((y-y0) == size)
            {
                y=y0;
                x++;
                if(x >= 128)    /* 超区域了 */
                    return;
                break;
            }
        }
    }
}

/**
 * @brief 显示字符串
 * @param  obj         OLED对象
 * @param  x           x坐标
 * @param  y           y坐标
 * @param  p           字符串起始地址
 * @param  size        字体大小
 */
void OledShowString( Oled_t *obj, uint8_t x, uint8_t y, const uint8_t *p, uint8_t size)
{	
    while((*p<='~') && (*p>=' ')) /* 判断是不是非法字符 */
    {
        if(x > (128-(size/2)))
        {
            x = 0;
            y += size;
        }
        if(y > (64-size))
        {
            y=x=0;
            OledClear( obj );
        }
        OledShowChar( obj, x, y, *p, size, 1 );
        x += size/2;
        p++;
    }
}

/**
 * @brief m^n函数
 */
static uint32_t mypow( uint8_t m, uint8_t n )
{
    uint32_t result = 1;
    while(n--)
        result *= m;
    return result;
}

/**
 * @brief 显示2个数字
 * @param  obj         OLED对象
 * @param  x           x坐标
 * @param  y           y坐标
 * @param  num         数值(0~4294967295)
 * @param  len         数字的位数
 * @param  size        字体大小
 */
void OledShowNum( Oled_t *obj, uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size )
{
    uint8_t t, temp;
    uint8_t enshow = 0;
    for(t=0; t<len; t++)
    {
        temp = (num/mypow(10, len-t-1)) % 10;
        if(enshow==0 && t<(len-1))
        {
            if(temp == 0)
            {
                OledShowChar( obj, x+(size/2)*t, y, ' ', size, 1 );
                continue;
            }
            else
                enshow = 1; 
        }
        OledShowChar( obj, x+(size/2)*t, y, temp+'0', size, 1 );
    }
}

#endif	/* DRV_OLED_EN */
/*********** (C) COPYRIGHT 2020 FiberHome *****END OF FILE****/
